import time
from multiprocessing import Queue, current_process, Lock
from queue import Empty

from .common_func.msg_queue_count import COUNT_FILE, MsgQueueCount, timestamp_2_str
from .common_func.print_logger import PrintLogger


class TopicExistError(Exception):
    pass


class TopicNotFoundError(Exception):
    pass


class UQueueServ:
    def __init__(self, queue_size: int = 0, logger = PrintLogger, count_file: str = COUNT_FILE, send_block=0):
        """

        :param queue_size: 主题队列的maxsize
        :param logger
        :param count_file 计数器文件
        :param send_block 发送阻塞模式设定，0表示不阻塞，队列满时直接丢弃消息不发送，N表示阻塞N秒等待队列不满，等待超时丢弃新消息
        """
        self.queue_size = queue_size
        self.client_info = dict()
        self.queue_info = dict()
        self.logger = logger
        self.count_file = count_file
        self.count_file_lock = Lock()
        self.send_block = send_block


    def create_topic(self, topic: str, exist_raise: bool = False, queue_size: int = 0):
        """
        Create queue topic
        :param queue_size:
        :param topic:
        :param exist_raise: raise exception if true, else pass
        :return:
        """
        if not topic:
            raise SyntaxError("Topic can not be empty")

        if self.queue_info.get(topic, None) is not None:
            if exist_raise:
                raise TopicExistError
            else:
                return
        if queue_size:
            self.queue_info[topic] = Queue(queue_size)
        else:
            self.queue_info[topic] = Queue(self.queue_size)
        self.logger.info(f"[UQueueServ]::Create Topic {topic} Success,size {self.queue_size if not queue_size else queue_size}")

    def gen_producer(self, topic: str):
        """
        生成一个生产者，返回生产者句柄
        :param topic:
        :return:
        """
        if topic not in self.queue_info:
            raise TopicNotFoundError
        channel = self.queue_info[topic]
        return UQueueProducer(channel, self.logger, count_file=self.count_file,
                              count_file_lock=self.count_file_lock, send_block=self.send_block, topic=topic)

    def gen_consumer(self, topic: str):
        """
        生成一个消费者，返回消费者句柄
        :param topic:
        :return:
        """
        if topic not in self.queue_info:
            raise TopicNotFoundError
        channel = self.queue_info[topic]
        return UQueueConsumer(channel, self.logger, count_file=self.count_file, count_file_lock=self.count_file_lock, topic=topic)


class UQueueProducer:
    def __init__(self, channel, logger, count_unit: int = 10000, count_file: str = COUNT_FILE,
                 count_file_lock: Lock = None, send_block=0, topic=""):
        self.last_send_time = time.time()
        self.queue:Queue = channel
        self.msg_list = list()
        self.logger = logger
        self.count = 0
        self.count_unit = count_unit
        self.count_name = current_process().name.split("__")[0]
        self.count_file = count_file
        self.count_file_lock = count_file_lock
        self.send_block = send_block
        self.topic = topic

    def set_count_name(self, name):
        """
        设置count文件key
        :param name:
        :return:
        """
        self.count_name = name
        with self.count_file_lock:
            data = MsgQueueCount.get_count_content(self.count_file, self.logger)

            produce_data = data.get("produce", dict())
            last_count_info = produce_data.get(self.count_name, None)
            if last_count_info is None or not isinstance(last_count_info, dict):
                produce_data[self.count_name] = MsgQueueCount.get_init_count_info()
            else:
                cur_time = time.time()
                cur_str_time = timestamp_2_str(cur_time)
                last_count_info["last_record_time"] = cur_time
                last_count_info["last_record_str_time"] = cur_str_time
                produce_data[self.count_name] = last_count_info

            data["produce"] = produce_data

            MsgQueueCount.write_count(self.count_file, data)
            self.logger.debug(f"{self.count_name} Producer count data: {data}")

    def send(self, msg):
        """
        发送消息
        :param msg: 消息内容
        :return: True-发送成功；False-发送失败
        """
        if not msg:
            return False

        if self.queue.full():
            remain_time = self.send_block
            while remain_time > 0:
                time.sleep(0.1)
                remain_time = remain_time - 0.1
                if not self.queue.full():
                    return self._send(msg)
            self.logger.info(f"队列{self.topic}缓存已满，直接丢弃消息，当前时间：{time.time()}")
            return False
        else:
            return self._send(msg)

    def _send(self, msg):
        try:
            self.queue.put(msg)
            self.send_count()
        except Exception as e:
            self.logger.debug(f"[UQueueProducer]::topic {self.topic} send failed, retry, error: {e}, "
                              f"error line:{e.__traceback__.tb_lineno}")
            try:
                self.queue.put(msg)
                self.send_count()
            except Exception as e:
                self.logger.error(f"[UQueueProducer]::topic {self.topic} send failed and retry send still failed, send msg: {msg}, "
                                  f"error: {e}, error line:{e.__traceback__.tb_lineno}")
                return False

        return True

    def send_count(self):
        self.count += 1
        if self.count % self.count_unit == 0:
            self.count = 0
            self.commit_count()

    def commit_count(self):
        with self.count_file_lock:
            data = MsgQueueCount.get_count_content(self.count_file, self.logger)

            produce_data = data.get("produce", dict())
            last_count_info = produce_data.get(self.count_name, None)
            if last_count_info is None or not isinstance(last_count_info, dict):
                last_count_info = MsgQueueCount.get_init_count_info()
            count_info = MsgQueueCount.assembly_count_info(last_count_info, count_unit=self.count_unit)
            produce_data[self.count_name] = count_info
            data["produce"] = produce_data

            MsgQueueCount.write_count(self.count_file, data)
            self.logger.debug(f"{self.count_name} Producer count data: {data}")


class UQueueConsumer:
    def __init__(self, channel, logger, count_unit: int = 10000, count_file: str = COUNT_FILE, count_file_lock: Lock = None, topic=""):
        self.topic = topic
        self.queue = channel
        self.msg_list = list()
        self.logger = logger
        self.count = 0
        self.count_unit = count_unit
        self.count_name = current_process().name.split("__")[0]
        self.count_file = count_file
        self.count_file_lock = count_file_lock

    def set_count_name(self, name):
        self.count_name = name
        with self.count_file_lock:
            data = MsgQueueCount.get_count_content(self.count_file, self.logger)

            consume_data = data.get("consume", dict())
            last_count_info = consume_data.get(self.count_name, None)

            if last_count_info is None or not isinstance(last_count_info, dict):
                consume_data[self.count_name] = MsgQueueCount.get_init_count_info()
            else:
                cur_time = time.time()
                cur_str_time = timestamp_2_str(cur_time)
                last_count_info["last_record_time"] = cur_time
                last_count_info["last_record_str_time"] = cur_str_time
                consume_data[self.count_name] = last_count_info

            data["consume"] = consume_data
            MsgQueueCount.write_count(self.count_file, data)
            self.logger.debug(f"{self.count_name} Consumer count data: {data}")

    def poll(self, timeout=None):
        """
        Consumes a single message
        :param timeout
        :return: True-发送成功；False-发送失败
        """
        data = None
        try:
            data = self.queue.get(timeout=timeout)
            self.poll_count()
            return data
        except Empty:
            return None
        except Exception as e:
            self.logger.debug(f"[UQueueConsumer]::topic {self.topic} recv failed, retry, error: {e}, "
                              f"error line:{e.__traceback__.tb_lineno}")
            try:
                data = self.queue.get()
                self.poll_count()
                return data
            except Empty:
                return None
            except Exception as e:
                self.logger.error(f"[UQueueConsumer]::topic {self.topic} recv failed and retry still failed, recv data: {data}, "
                                  f"error: {e}, error line:{e.__traceback__.tb_lineno}")
                return None

    def poll_count(self):
        self.count += 1
        if self.count % self.count_unit == 0:
            self.count = 0
            self.commit_count()

    def commit_count(self):
        with self.count_file_lock:
            data = MsgQueueCount.get_count_content(self.count_file, self.logger)

            consume_data = data.get("consume", dict())
            last_count_info = consume_data.get(self.count_name, None)
            if last_count_info is None or not isinstance(last_count_info, dict):
                last_count_info = MsgQueueCount.get_init_count_info()
            count_info = MsgQueueCount.assembly_count_info(last_count_info, count_unit=self.count_unit)
            consume_data[self.count_name] = count_info
            data["consume"] = consume_data

            MsgQueueCount.write_count(self.count_file, data)
            self.logger.debug(f"{self.count_name} Consumer count data: {data}")